//
//  HttpHelper.swift
//  iUtil
//
//  Created by wendachuan on 19/3/22.
//  Copyright © 2019年 wendachuan. All rights reserved.
//

import AFNetworking

/// http请求助手
open class HttpHelper: NSObject {
    
    /// http method
    public enum HttpMethod {
        case post
        case get
        case delete
        case put
    }
    
    // http请求超时时间
    public var requestTimeout = TimeInterval(10)

    // 基础url
    fileprivate let baseUrl: String
    
    // http委托
    public var delegate: HttpHelperDelegate?
    
    // 请求值序列化类型
    public var requestSerializationType = HttpRequestSerializationType.json
    
    // 返回值序列化类型
    public var responseSerializationType = HttpResponseSerializationType.http

    public init(baseUrl: String) {
        self.baseUrl = baseUrl
    }
    
    /// 构造请求地址
    ///
    /// - Parameters:
    ///   - shortAddress: 短地址
    ///   - parameters: 请求url携带的参数
    ///   - success: 成功时调用的闭包
    ///   - failure: 失败时调用的闭包
    /// - Returns: 请求地址
    class func constructAddress(shortAddress: String,
                                parameters: [String : Any]? = nil,
                                success: @escaping (_ urlAddress: String) -> Void,
                                failure: @escaping (_ error: NSError) -> Void) {
        var addressWithParameters = shortAddress
        var paraFlagSet = shortAddress.contains("?")
        if let parameters = parameters {
            for (parameterName, parameterValue) in parameters {
                if !paraFlagSet {
                    paraFlagSet = true
                    addressWithParameters.append(Character("?"))
                } else {
                    addressWithParameters.append(Character("&"))
                }
                
                addressWithParameters.append("\(parameterName)=\(parameterValue)")
            }
        }
        
        if let urlAddress = addressWithParameters.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) {
            success(urlAddress)
        } else {
            failure(NSError(errorCode: 0, errorDescription: "地址:[\(addressWithParameters)]不能转化为合法的URL地址"))
        }
    }
    
    /// 构造请求参数(过滤掉nil值)
    ///
    /// - Parameters:
    ///   - shortAddress: 短地址
    ///   - parameters: 请求url携带的参数
    ///   - success: 成功时调用的闭包
    ///   - failure: 失败时调用的闭包
    /// - Returns: 请求地址
    public class func constructParameters(_ parameters: Dictionary<String, Any?>) -> Dictionary<String, Any> {
        var filteredParameters = Dictionary<String, Any>.init()
        for pair in parameters {
            if let value = pair.value {
                filteredParameters[pair.key] = value
            }
        }
        return filteredParameters
    }

    /// http POST请求(异步)
    ///
    /// - parameter shortAddress: 短地址
    /// - parameter parameters: 请求url携带的参数
    /// - parameter header: http请求头部
    /// - parameter requestObject: 请求参数
    /// - parameter certificates: https证书数据
    /// - parameter requestSerializationType: http请求序列化类型
    /// - parameter responseSerializationType: http返回值序列化类型
    /// - parameter success: 成功时调用的闭包
    /// - parameter failure: 失败时调用的闭包
    open func post(_ shortAddress: String,
                   parameters: [String : Any]? = nil,
                   header: [String : String]? = nil,
                   requestObject: Any? = nil,
                   certificates: Set<Data>? = nil,
                   requestSerializationType: HttpRequestSerializationType? = nil,
                   responseSerializationType: HttpResponseSerializationType? = nil,
                   success: @escaping (_ responseObject: Any?) -> Void,
                   failure: @escaping (_ error: NSError) -> Void ) {
        let pair = delegate?.injectBeforeRequest(self, shortAddress: shortAddress, method: .post, parameters: parameters, requestHeader: header, requestObject: requestObject)
        HttpHelper.constructAddress(shortAddress: shortAddress, parameters: pair?.newParameters, success: { [weak self] (urlAddress) in
            guard let strongSelf = self else {
                failure(NSError.init(errorCode: -1, errorDescription: "weak self"))
                return
            }
            
            let httpSessionManager = HttpHelper.makeHTTPSessionManager(baseUrl: strongSelf.baseUrl,
                                                                       header: pair?.newRequestHeader,
                                                                       certificates: certificates,
                                                                       requestTimeout: strongSelf.requestTimeout,
                                                                       requestSerializationType: requestSerializationType == nil ? strongSelf.requestSerializationType : requestSerializationType!,
                                                                       responseSerializationType: responseSerializationType == nil ? strongSelf.responseSerializationType : responseSerializationType!)
            httpSessionManager.post(urlAddress, parameters: pair?.newRequestObject, progress: nil, success: {
                (sessionDataTask, responseObject) -> Void in
                strongSelf.parseResponse(sessionDataTask.response, responseObject: responseObject, success: success, failure: failure)
            }) { (sessionDataTask, error) -> Void in
                failure(NSError(errorCode: 0, errorDescription: error.localizedDescription))
            }
        }, failure: failure)
    }

    /// http GET请求(异步)
    ///
    /// - parameter shortAddress: 短地址
    /// - parameter parameters: get请求url携带的参数
    /// - parameter header: http请求头部
    /// - parameter requestObject: 请求参数
    /// - parameter certificates: https证书数据
    /// - parameter requestSerializationType: http请求序列化类型
    /// - parameter responseSerializationType: http返回值序列化类型
    /// - parameter success: 成功时调用的闭包
    /// - parameter failure: 失败时调用的闭包
    open func get(_ shortAddress: String,
                  parameters: [String : Any]? = nil,
                  header: [String : String]? = nil,
                  requestObject: Any? = nil,
                  certificates: Set<Data>? = nil,
                  requestSerializationType: HttpRequestSerializationType? = nil,
                  responseSerializationType: HttpResponseSerializationType? = nil,
                  success: @escaping (_ responseObject: Any?) -> Void,
                  failure: @escaping (_ error: NSError) -> Void ) {
        let pair = delegate?.injectBeforeRequest(self, shortAddress: shortAddress, method: .get, parameters: parameters, requestHeader: header, requestObject: requestObject)
        HttpHelper.constructAddress(shortAddress: shortAddress, parameters: pair?.newParameters, success: { [weak self] (urlAddress) in
            guard let strongSelf = self else {
                failure(NSError.init(errorCode: -1, errorDescription: "weak self"))
                return
            }
            
            let httpSessionManager = HttpHelper.makeHTTPSessionManager(baseUrl: strongSelf.baseUrl,
                                                                       header: pair?.newRequestHeader,
                                                                       certificates: certificates,
                                                                       requestTimeout: strongSelf.requestTimeout,
                                                                       requestSerializationType: requestSerializationType == nil ? strongSelf.requestSerializationType : requestSerializationType!,
                                                                       responseSerializationType: responseSerializationType == nil ? strongSelf.responseSerializationType : responseSerializationType!)
            httpSessionManager.get(urlAddress, parameters: pair?.newRequestObject, progress: nil, success: { (sessionDataTask, responseObject) -> Void in
                strongSelf.parseResponse(sessionDataTask.response, responseObject: responseObject, success: success, failure: failure)
            }) { (sessionDataTask, error) -> Void in
                failure(NSError(errorCode: 0, errorDescription: error.localizedDescription))
            }
        }, failure: failure)
    }

    /// http PUT请求(异步)
    ///
    /// - parameter shortAddress: 短地址
    /// - parameter header: http请求头部
    /// - parameter requestObject: 请求参数
    /// - parameter certificates: https证书数据
    /// - parameter requestSerializationType: http请求序列化类型
    /// - parameter responseSerializationType: http返回值序列化类型
    /// - parameter success: 成功时调用的闭包
    /// - parameter failure: 失败时调用的闭包
    open func put(_ shortAddress: String,
                  parameters: [String : Any]? = nil,
                  header: [String : String]? = nil,
                  requestObject: Any? = nil,
                  certificates: Set<Data>? = nil,
                  requestSerializationType: HttpRequestSerializationType? = nil,
                  responseSerializationType: HttpResponseSerializationType? = nil,
                  success: @escaping (_ responseObject: Any?) -> Void,
                  failure: @escaping (_ error: NSError) -> Void ) {
        let pair = delegate?.injectBeforeRequest(self, shortAddress: shortAddress, method: .put, parameters: parameters, requestHeader: header, requestObject: requestObject)
        HttpHelper.constructAddress(shortAddress: shortAddress, parameters: pair?.newParameters, success: { [weak self] (urlAddress) in
            guard let strongSelf = self else {
                failure(NSError.init(errorCode: -1, errorDescription: "weak self"))
                return
            }
            
            let httpSessionManager = HttpHelper.makeHTTPSessionManager(baseUrl: strongSelf.baseUrl,
                                                                       header: pair?.newRequestHeader,
                                                                       certificates: certificates,
                                                                       requestTimeout: strongSelf.requestTimeout,
                                                                       requestSerializationType: requestSerializationType == nil ? strongSelf.requestSerializationType : requestSerializationType!,
                                                                       responseSerializationType: responseSerializationType == nil ? strongSelf.responseSerializationType : responseSerializationType!)
            httpSessionManager.put(urlAddress, parameters: pair?.newRequestObject, success: { (sessionDataTask, responseObject) -> Void in
                strongSelf.parseResponse(sessionDataTask.response, responseObject: responseObject, success: success, failure: failure)
            }) { (sessionDataTask, error) -> Void in
                failure(NSError(errorCode: 0, errorDescription: error.localizedDescription))
            }
        }, failure: failure)
    }

    /// http DELETE请求(异步)
    ///
    /// - parameter shortAddres: 短地址
    /// - parameter header: http请求头部
    /// - parameter requestObject: 请求参数
    /// - parameter certificates: https证书数据
    /// - parameter requestSerializationType: http请求序列化类型
    /// - parameter responseSerializationType: http返回值序列化类型
    /// - parameter success: 成功时调用的闭包
    /// - parameter failure: 失败时调用的闭包
    open func delete(_ shortAddress: String,
                     parameters: [String : Any]? = nil,
                     header: [String : String]? = nil,
                     requestObject: Any? = nil,
                     certificates: Set<Data>? = nil,
                     requestSerializationType: HttpRequestSerializationType? = nil,
                     responseSerializationType: HttpResponseSerializationType? = nil,
                     success: @escaping (_ responseObject: Any?) -> Void,
                     failure: @escaping (_ error: NSError) -> Void ) {
        let pair = delegate?.injectBeforeRequest(self, shortAddress: shortAddress, method: .delete, parameters: parameters, requestHeader: header, requestObject: requestObject)
        HttpHelper.constructAddress(shortAddress: shortAddress, parameters: pair?.newParameters, success: { [weak self] (urlAddress) in
            guard let strongSelf = self else {
                failure(NSError.init(errorCode: -1, errorDescription: "weak self"))
                return
            }
            
            let httpSessionManager = HttpHelper.makeHTTPSessionManager(baseUrl: strongSelf.baseUrl,
                                                                       header: pair?.newRequestHeader,
                                                                       certificates: certificates,
                                                                       requestTimeout: strongSelf.requestTimeout,
                                                                       requestSerializationType: requestSerializationType == nil ? strongSelf.requestSerializationType : requestSerializationType!,
                                                                       responseSerializationType: responseSerializationType == nil ? strongSelf.responseSerializationType : responseSerializationType!)
            httpSessionManager.delete(urlAddress, parameters: pair?.newRequestObject, success: { (sessionDataTask, responseObject) -> Void in
                strongSelf.parseResponse(sessionDataTask.response, responseObject: responseObject, success: success, failure: failure)
            }) { (sessionDataTask, error) -> Void in
                failure(NSError(errorCode: 0, errorDescription: error.localizedDescription))
            }
        }, failure: failure)
    }

    /// 构建http session manager
    ///
    /// - parameter baseUrl: 基地址
    /// - parameter header: http请求头部
    /// - parameter certificates: https证书数据
    /// - parameter requestTimeout: 请求超时时间(秒)
    /// - parameter responseSerializationType: 返回数据序列化类型
    /// - returns: AFHTTPSessionManager对象
    fileprivate static func makeHTTPSessionManager(baseUrl: String, header: [String : String]? = nil, certificates: Set<Data>? = nil, requestTimeout: TimeInterval = 10, requestSerializationType: HttpRequestSerializationType = .json, responseSerializationType: HttpResponseSerializationType = .http) -> AFHTTPSessionManager {
        let sessionConfiguration = URLSessionConfiguration.default
        sessionConfiguration.timeoutIntervalForRequest = requestTimeout
        sessionConfiguration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
        sessionConfiguration.httpShouldSetCookies = false

        let httpSessionManager = AFHTTPSessionManager(baseURL: URL.validURLFromString(string: baseUrl), sessionConfiguration: sessionConfiguration)
        
        switch requestSerializationType {
        case .json:
            httpSessionManager.requestSerializer = AFJSONRequestSerializer(writingOptions: JSONSerialization.WritingOptions.prettyPrinted)
        default:
            httpSessionManager.requestSerializer = AFHTTPRequestSerializer.init()
            httpSessionManager.requestSerializer.setQueryStringSerializationWith { (request, object, error) -> String in
                guard let str = object as? String else {
                    error?.pointee = NSError.init(errorCode: -1, errorDescription: "Request object is not a string.")
                    return ""
                }
                return str
            }
        }
        
        switch responseSerializationType {
        case .json:
            httpSessionManager.responseSerializer = AFJSONResponseSerializer()
        case .xml:
            httpSessionManager.responseSerializer = AFXMLParserResponseSerializer()
        default:
            httpSessionManager.responseSerializer = AFHTTPResponseSerializer()
        }
        
        httpSessionManager.responseSerializer.acceptableStatusCodes = nil
        httpSessionManager.completionQueue = DispatchQueue(label: "HttpHelper", attributes: [])//dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)

        if let requestHeader = header {
            for (fieldName, fieldValue) in requestHeader {
                httpSessionManager.requestSerializer.setValue(fieldValue, forHTTPHeaderField: fieldName)
            }
        }

        if baseUrl.lowercased().starts(with: "https") {
            if let certificatesSet = certificates {
                let securityPolicy = AFSecurityPolicy(pinningMode: AFSSLPinningMode.certificate, withPinnedCertificates: certificatesSet)
                securityPolicy.allowInvalidCertificates = true
                securityPolicy.validatesDomainName = false
                httpSessionManager.securityPolicy = securityPolicy
            }
        }

        return httpSessionManager
    }

    /// 分析http返回对象
    ///
    /// - parameter response: http请求返回
    /// - parameter responseObject: 返回的JSON对象
    /// - parameter success: 成功时调用的闭包
    /// - parameter failure: 失败时调用的闭包
    public static func parseHttpResponse(_ response: URLResponse?, responseObject: Any?, success: (_ responseObject: Any?) -> Void, failure: (_ error: NSError) -> Void ) {
        if let httpResponseObject = responseObject {
            if let httpResponse = response as? HTTPURLResponse {
                let jsonResponseSerializer = AFJSONResponseSerializer()
                jsonResponseSerializer.acceptableStatusCodes = nil
                jsonResponseSerializer.acceptableContentTypes = nil
                let jsonResponseObject = jsonResponseSerializer.responseObject(for: response, data: httpResponseObject as? Data, error: nil)

                var xmlResponseObject: XMLParser?
                if nil == jsonResponseObject {
                    let xmlResponseSerializer = AFXMLParserResponseSerializer()
                    xmlResponseSerializer.acceptableStatusCodes = nil
                    xmlResponseSerializer.acceptableContentTypes = nil
                    xmlResponseObject = xmlResponseSerializer.responseObject(for: response, data: httpResponseObject as? Data, error: nil) as? XMLParser
                }

                if httpResponse.statusCode >= HttpStatusCode.ok.rawValue && httpResponse.statusCode < 300 {
                    if jsonResponseObject != nil {
                        success(jsonResponseObject)
                    } else if xmlResponseObject != nil {
                        success(xmlResponseObject)
                    } else {
                        success(httpResponseObject)
                    }
                } else {
                    var error: NSError!
                    if jsonResponseObject != nil {
                        var err: NSError? = nil
                        if let dict = JsonHelper.require(jsonResponseObject, asType: NSDictionary.self, error: &err) {
                            error = NSError(errorCode: httpResponse.statusCode, errorDescription: "\(dict)")
                        } else if let arr = JsonHelper.require(jsonResponseObject, asType: NSArray.self, error: &err) {
                            error = NSError(errorCode: httpResponse.statusCode, errorDescription: "\(arr.componentsJoined(by: ","))")
                        } else {
                            error = NSError(errorCode: httpResponse.statusCode, errorDescription: "\(String(describing: jsonResponseObject))")
                        }
                    } else if let responseData = responseObject as? Data {
                        let responseString = String.toNoneNIL(String(data: responseData, encoding: String.Encoding.utf8))
                        error = NSError(errorCode: httpResponse.statusCode, errorDescription: "\(responseString)")
                    } else {
                        error = NSError(errorCode: httpResponse.statusCode, errorDescription: "http返回对象为空")
                    }
                    failure(error)
                }
            } else {
                failure(NSError(errorCode: 0, errorDescription: "http请求没有返回"))
            }
        } else {
            failure(NSError(errorCode: 0, errorDescription: "返回的对象为空"))
        }
    }

    /// 分析http返回对象
    ///
    /// - parameter response: http请求返回
    /// - parameter responseObject: 返回的JSON对象
    /// - parameter success: 成功时调用的闭包
    /// - parameter failure: 失败时调用的闭包
    open func parseResponse(_ response: URLResponse?, responseObject: Any?, success: (_ object: Any?) -> Void, failure: (_ error: NSError) -> Void ) {
        guard let httpResponseObject = responseObject else {
            failure(NSError(errorCode: 0, errorDescription: "返回的对象为空"))
            return
        }

        guard let httpResponse = response as? HTTPURLResponse else {
            failure(NSError(errorCode: 0, errorDescription: "response不是http response"))
            return
        }

        if httpResponse.statusCode >= HttpStatusCode.ok.rawValue && httpResponse.statusCode < 300 {
            delegate?.parseResponse(self, httpResponse: httpResponse, responseObject: httpResponseObject, success: success, failure: failure)
        } else {
            if let error = delegate?.parseResponseObjectWhenFailed(self, httpResponse: httpResponse, responseObject: httpResponseObject) {
                failure(error)
            }
        }
    }
    
    /// 分析返回的对象
    ///
    /// - Parameters:
    ///   - httpResponse:
    ///   - responseObject: 返回的对象
    ///   - success: 解析成功后调用的闭包
    ///   - failure: 解析失败后调用的闭包
    /// - Returns: 解析出的错误
    open class func parseResponseObject(httpResponse: HTTPURLResponse,
                                        responseObject: Any,
                                        success: (_ object: Any?) -> Void,
                                        failure: (_ error: NSError) -> Void) {
        
        let jsonResponseSerializer = AFJSONResponseSerializer()
        jsonResponseSerializer.acceptableStatusCodes = nil
        jsonResponseSerializer.acceptableContentTypes = nil
        let jsonResponseObject = jsonResponseSerializer.responseObject(for: httpResponse, data: responseObject as? Data, error: nil)
        if let jsonResponseObject = jsonResponseObject {
            success(jsonResponseObject)
            return
        } else {
            let xmlResponseSerializer = AFXMLParserResponseSerializer()
            xmlResponseSerializer.acceptableStatusCodes = nil
            xmlResponseSerializer.acceptableContentTypes = nil
            if let xmlResponseObject = xmlResponseSerializer.responseObject(for: httpResponse, data: responseObject as? Data, error: nil) as? XMLParser {
                success(xmlResponseObject)
            }
        }
        
        var error: NSError!
        if jsonResponseObject != nil {
            var err: NSError? = nil
            if let dict = JsonHelper.require(jsonResponseObject, asType: NSDictionary.self, error: &err) {
                error = NSError(errorCode: httpResponse.statusCode, errorDescription: "\(dict)")
            } else if let arr = JsonHelper.require(jsonResponseObject, asType: NSArray.self, error: &err) {
                error = NSError(errorCode: httpResponse.statusCode, errorDescription: "\(arr.componentsJoined(by: ","))")
            } else {
                error = NSError(errorCode: httpResponse.statusCode, errorDescription: "\(String(describing: jsonResponseObject))")
            }
        } else if let responseData = responseObject as? Data {
            let responseString = String.toNoneNIL(String(data: responseData, encoding: String.Encoding.utf8))
            error = NSError(errorCode: httpResponse.statusCode, errorDescription: "\(responseString)")
        } else {
            error = NSError(errorCode: httpResponse.statusCode, errorDescription: "http返回对象为空")
        }
        failure(error)
    }
}

/// http请求内容序列化类型
public enum HttpRequestSerializationType {
    case http
    case json
}

/// http返回值序列化类型
public enum HttpResponseSerializationType {
    case http
    case json
    case xml
}

/// HttpHelper代理协议
public protocol HttpHelperDelegate: class {
    /// 在请求前注入
    ///
    /// - Parameters:
    ///   - helper:
    ///   - shortAddress: 请求短地址
    ///   - method: http method
    ///   - parameters 携带在url地址中的参数
    ///   - requestHeader: 请求头
    ///   - requestObject: 请求对象
    func injectBeforeRequest(_ helper: HttpHelper,
                             shortAddress: String,
                             method: HttpHelper.HttpMethod,
                             parameters: [String: Any]?,
                             requestHeader: [String : String]?,
                             requestObject: Any?) -> (newParameters: [String: Any]?, newRequestHeader: [String : String]?, newRequestObject:  Any?)
    
    /// 当http请求成功时，调用此函数可以分析返回的对象，解析其中的错误信息
    ///
    /// - Parameters:
    ///   - helper:
    ///   - response: 返回的请求
    ///   - responseObject: 返回的对象
    ///   - success: 解析成功后调用的闭包
    ///   - failure: 解析失败后调用的闭包
    /// - Returns: 解析出的错误
    func parseResponse(_ helper: HttpHelper,
                       httpResponse: HTTPURLResponse,
                       responseObject: Any,
                       success: (_ object: Any?) -> Void,
                       failure: (_ error: NSError) -> Void)
    
    /// 当http请求失败时，表明出现了错误，此时回调此函数解析错误信息
    ///
    /// - parameter helper:
    /// - parameter response: 返回的请求
    /// - parameter responseObject: 返回的对象
    /// - returns: 解析出的错误
    func parseResponseObjectWhenFailed(_ helper: HttpHelper, httpResponse: HTTPURLResponse, responseObject: Any) -> NSError?

    /// 获得证书数据
    ///
    /// - returns: 证书数据
    static func getCertificates() -> Set<Data>?
}

/// HttpHelperDelegate可选接口定义
public extension HttpHelperDelegate {
    /// 在请求前注入
    ///
    /// - Parameters:
    ///   - helper:
    ///   - shortAddress: 请求短地址
    ///   - method: http method
    ///   - parameters 携带在url地址中的参数
    ///   - requestHeader: 请求头
    ///   - requestObject: 请求对象
    func injectBeforeRequest(_ helper: HttpHelper,
                             shortAddress: String,
                             method: HttpHelper.HttpMethod,
                             parameters: [String: Any]?,
                             requestHeader: [String : String]?,
                             requestObject: Any?) -> (newParameters: [String: Any]?, newRequestHeader: [String : String]?, newRequestObject:  Any?) {
        return (parameters, requestHeader, requestObject)
    }
    
    /// 当http请求成功时，调用此函数可以分析返回的对象，解析其中的错误信息
    ///
    /// - Parameters:
    ///   - helper:
    ///   - response: 返回的请求
    ///   - responseObject: 返回的对象
    ///   - success: 解析成功后调用的闭包
    ///   - failure: 解析失败后调用的闭包
    /// - Returns: 解析出的错误
    func parseResponse(_ helper: HttpHelper,
                       httpResponse: HTTPURLResponse,
                       responseObject: Any,
                       success: (_ object: Any?) -> Void,
                       failure: (_ error: NSError) -> Void) {
        return
    }
    
    /// 当http请求失败时，表明出现了错误，此时回调此函数解析错误信息
    ///
    /// - parameter helper:
    /// - parameter response: 返回的请求
    /// - parameter responseObject: 返回的对象
    /// - returns: 解析出的错误
    func parseResponseObjectWhenFailed(_ helper: HttpHelper, httpResponse: HTTPURLResponse, responseObject: Any) -> NSError? {
        return nil
    }
    
    /// 获得证书数据
    ///
    /// - returns: 证书数据
    static func getCertificates() -> Set<Data>? { return nil }
}
